cpp11
统一初始化
cpp11以前:
int x; //全局作用域时,默认初始化
int x = 7; //值初始化
int a[] = {7, 8}; //聚合初始化
string s; //默认构造函数初始化
vector<int> v(10); //构造函数初始化
cpp11后:统一初始化方式,让内置类型和自定义类型可以使用一样的初始化方式
int a{5};
int a = {5};
int a[] {7, 8};
int a[] = {7, 8};
vector<int> v = {7, 8};
vector<int> v{7, 8};
int func(vector<int>);
int i = func({1,2,3});
struct X {
vector<int> v;
int a[];
X() : v{1, 2}, a{3, 4} {}//成员初始化器
}
vector<int>* p = new vector<int>{1,2,3,4};//new表达式
X x {};//默认初始化
tempalte<typename T> int foo(T);
int z = foo{X{1}};//显示构造
//花括号初始化不允许窄化转换
double d = 7.2;
int x = d; //OK,x = 7
int y{d}; //error
//自定义类型可以通过将标准库类型 initializer_list 用作`初始化器列表构造函数`的参数,实现统一初始化
tempalte <typename T>
class vector {
public:
vector(initializer_list<T>);//初始化器列表构造函数
}
vector<int> v3 {1,2,3};
类型推导auto和decltype
使用推导类型可以缩减类型名称。
使用auto来避免类型名称的多余重复。
C++11的auto仅支持推导变量类型,和弱化的支持函数返回类型推导
auto X {
public:
auto f() -> int;
}
void use(int x, char* p){
auto x2 = x*2;
auto ch = p[x];
auto p2 = p+2;
}
auto推导不出引用类型,需要手动声明;或者使用decltype
template<typename T> void f(T& r){
auto v = r; //v 是 T
decltype(r) r2 = r; //r2是 T&
auto &r3 = r; //r3是 T&
}
auto一般会忽略顶层const,保留底层const。
特殊:
- 当原变量被取地址时,它的顶层const会变成底层const,从而得到保留
- 当原变量被引用时,它的顶层const也会保留
const int ci = 32, &cr = ci;
auto b = ci; // b is int
auto c = cr; // c is int
auto d = &ci; // d is const int *,这里是底层const
auto &q = ci; // q is int& const
auto &w = 32; //error:w is int&,32 need const int&
const auto &e = 32; //e is const int&
const int ci = 32, &cr = ci;
auto b = ci; // b is int
auto c = cr; // c is int
auto d = &ci; // d is const int *,这里是底层const
auto &q = ci; // q is int& const
auto &w = 32; //error:w is int&,32 need const int&
const auto &e = 32; //e is const int&
顶层const:描述变量本身
底层const:描述变量指向内容
auto推导时要有一致性:
const int i = 32;
int j = 32;
auto a = i, &b = j; //OK: a is int, b is int&, auto is int
auto &c = i, *p1 = &i; //OK: c is int& const, p1 is const int*, auto is const int(or int const)
auto &d = j, *p2 = &i; //error: d is int&, p2 is const int*,
//auto is different: int 、const int
decltype的结果类型与表达式形式密切相关:
- 使用不加括号的变量,返回变量的类型
- 使用加括号的变量,返回表达式的类型
变量是一种可以作为赋值语句左值的特俗表达式
int i = 0;
decltype(i) d;//d is int
decltype((i)) e = i;//e is int&
decltype((variable))(注意是双层括号)的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用时才是引用
范围for
范围for可以避免一些错误
void use(vector<int>& v, list<string>& list) {
for(int x : v) cout << x << "\n";
int sum = 0;
for(auto i : {1,2,3,4,5}) sum+=i;
for(string& s: list) s+=".cpp";
}
void error_use(vector<int>& v, list<string>& list) {
for(int i = 0; i < imax; ++i)
for(int j = 0; i < imax; ++j){} //错误的嵌套循环
for(int i = 0; i <= imax; ++i){} //多循环了一次的错误
}
移动语义
调用函数时,参数按值传递,会产生拷贝,尽管通过使用 const &可以巧妙避免传入参数的拷贝,但对返回值还是存在拷贝行为。
//连续加法的需求,让其返回值不能是指针
Matrix operator+(const Matrix&, const Matrix&);
为了避免返回值时进行大量的数据复制,需要确保在实现返回时,构造函数复制的只是(指向自由存储区上的数据的)句柄,而不是所有元素。
class Matrix {
double* elements; //指向所有元素的指针
public:
Matrix(Matrix&& a) //移动构造
{
elements = a.elements; //复制句柄
a.elements = nullptr; //现在 a 的析构函数不用做任何事情了
}
}
当用于初始化或赋值的源对象马上就会被销毁时,移动比拷贝要更好。
&&表示构造函数是一个移动构造函数,Matrix&& 称作右值引用,当用于模板参数时,右值引用的符号&&被叫做转发引用。
移动语义蕴含着性能上的重大好处:它消除了代价高昂的临时变量。
xvalue、lvalue、prvalue的区别:谈一谈 C++ 中的值的类型 - 知乎 (zhihu.com)
资源管理指针(智能指针)
C++11提供了智能指针:
- shared_ptr——代表共享所有权
- unique_ptr——代表独占所有权(取代C++98中的auto_ptr)
智能指针作用:减少资源泄露和悬空指针
shared_ptr是传统的计数指针:指向同一对象的所有指针共享一个计数器。往往需要wead_ptr的配合。
void ole_use(Args a){
auto q = new Blob(a);
//...
if(foo) throw Bad();//会泄露
if(bar) return; //会泄露
//...
delete q; //容易遗忘
}
void newer_sue(Args a){
auto p = unique_ptr<Blob>(new Blob(a));
//...
if(foo) throw Bad();//不会泄露
if(bar) return; //不会泄露
//...
}
// 除非真的需要指针,否则,简单地使用局部变量会更好
void simplest_use(Args a){
Blob b(a);
//...
if(foo) throw Bad();//不会泄露
if(bar) return; //不会泄露
//...
}
nullptr
int* p = 99-55-44; //空指针
int* q = 2; //错误:2是int,不是int*
C++继承了C语言的NULL,但NULL在C++中定义为0。
int p0 = nullptr;
int* p1 = 99-55-44; //可以,为了兼容性
int* p2 = NULL; //可以,为了兼容性
int f(char*);
int f(int);
int x1 = f(nullptr); //f(char*)
int x2 = f(0); //f(int)
constexpr函数
C++11之前进行常量表达式求值还是用的(无类型的)宏,与C一样。
constexpr的目的:
- 让编译期计算达到类型安全
- 在编译期计算,提高效率
- 支持嵌入式系统编程(尤其是ROM)
- 支持元编程(非模板元编程)
- 让编译期编程与“普通编程”非常相似
允许常量表达式中使用constexpr函数;允许在常量表达式中使用简单用户定义类型,叫字面量类型。
constexpr函数可以在编译期进行求值,无法访问非本地对象。
struct LengthInKM {
constexpr explicit LengthInKM(double d) : val(d) { }
constexpr double getValue() { return val; }
private:
double val;
};
LengthInKM marks[] = { LengthInKM(2.3), LengthInKM(0.76) };
void f(int x) {
int y1 = x;
constexpr int y2 = x; //错误,x不是常量,编译期x还不存在
constexpr int y3 = 77; //正确
}
用户定义字面量
内建类型有字面量,用户定义类型可通过重载字面量运算符(operator""
)实现自己的字面量,其本质是显示地使用构造函数。
constexpr Imaginary operator""i(long double x) {
return Imaginary(x);
}
Imaginary a = 3.14i; //从而complex<double>(1.2,3.4) == 1.2+3.4i;
原始字符串字面量
和C一样,C++使用反斜杠作为转义字符,如果在字符串字面量中使用反斜杠,需要使用双反斜杠。
通常的正则表达式模式广泛使用反斜杠和双引号,所以模式很快变得混乱和容易出错。
//美国邮政编码
regex pattern1 {"\\w{2}\\s*\\d{5}(-\\d{4})?"}; //普通字符串字面量
regex pattern2 {R"(\w{2}\s*\d{5}(-\d{4})?)"}; //原始字符串字面量
lambda表达式
C++11的lambda只支持捕获值或引用,C++14添加了移动捕获和泛型lambda
lambda表达式的中括号表示从周围环境捕获变量,小括号表示参数,返回值可以从返回语句推导出来,如果没有返回语句则不会返回任何东西。
声明捕获有三种方式:
- 隐式捕获:
&
捕获引用,=
捕获值 - 显示捕获列表
- 声明隐式捕获,并声明显示捕获列表:显示捕获列表的捕获方式同隐式捕获相反
有特别的捕获方式:
[this]
:通过引用捕获*this
(它是对象的左值),而不是通过值捕获指针[*this]
:捕获本地实体的值
#include <iostream>
#include <algorithm> // sort函数模板、for_each函数模板
#include <vector>
using namespace std;
//[capture list](parameter list)-> return type {function body}
int main ( )
{
auto f1 = []() { cout << "lambda test" << endl; };
f1();
//use paramter list
auto f2 = [](int a, int b) { cout << a << ' ' << b << endl; };
f2(3, 4);
//use capture list
int a = 1;
auto f3 = [a]() { cout << a << endl; }; //值捕获
f3();
auto f4 = [&a]() { a++; cout << a << endl; };//引用捕获
f4();
auto f5 = [&]() { a++; cout << a << endl; };//隐式捕获引用
f5();
auto f6 = [=]() { cout << a << endl; };//隐式捕获值
f6();
auto f7 = [=]() mutable { a++; cout << a << endl; }; //隐式捕获值,可变lambda,捕获的值在函数体改变不影响外面变量
f7();
cout << "after mutable" << a << endl;
auto f8 = [&,a]() { cout << a << endl; };
f8();
auto f9 = [=, &a]() { a++; cout << a << endl; };
f9();
}
元组tuple
元组是大小固定而成员类型可以不同的容器。可用于:
- 作为返回类型,用于需要超过一个返回类型的函数
- 同时赋值多个
void use() {
string a, aa;
vector b, bb;
double c, cc;
/* ... */
auto r = make_tuple(a, b, c); // 创建元组
tie(aa, bb, cc) = r; // 解包元组
// C++14后,可以逐个获取
aa = get<0>(r);
bb = get<1>(r);
cc = get<2>(r);
// C++17后,添加了结构化绑定,真正意义上的解包
auto [aaa, bbb, ccc] = r;
}